package xiongwei.jiang.service.impl;

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.CacheConfiguration;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.EntryUnit;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.expiry.ExpiryPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xiongwei.jiang.properties.EhcacheProperties;
import xiongwei.jiang.service.EhcacheService;
import xiongwei.jiang.util.StringUtils;

import java.io.File;
import java.time.Duration;
import java.time.temporal.ChronoUnit;

/**
 * 缓存服务接口实现类
 *
 * @author 天之蓝
 */
public class EhcacheServiceImpl implements EhcacheService {
    private static final Logger LOGGER = LoggerFactory.getLogger(EhcacheServiceImpl.class);

    private final EhcacheProperties ehcacheProperties;

    public EhcacheServiceImpl(EhcacheProperties ehcacheProperties) {
        this.ehcacheProperties = ehcacheProperties;
        this.init();
    }

    private ResourcePoolsBuilder resourcePoolsBuilder;

    private CacheManagerBuilder<CacheManager> cacheManagerBuilder;

    private ExpiryPolicy<Object, Object> expiryPolicy;

    private CacheManager cacheManager;

    public void init() {
        initEhcacheProperties();
        resourcePoolsBuilder = ResourcePoolsBuilder.newResourcePoolsBuilder()
                // JVM 内存中缓存的 key 数量
                .heap(ehcacheProperties.getHeap(), EntryUnit.ENTRIES)
                // 堆外内存大小，单位：MB
                .offheap(ehcacheProperties.getOffHeap(), MemoryUnit.MB);
        cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder();
        String diskDir = ehcacheProperties.getDiskDir();
        if (StringUtils.isNotBlank(diskDir)) {
            // 持久化到磁盘的大小，单位：MB（当且仅当 diskDir 不为空时生效）
            resourcePoolsBuilder.disk(ehcacheProperties.getDisk(), MemoryUnit.MB, true);
            File file = new File(diskDir);
            boolean isAbsolutePath = file.isAbsolute();
            diskDir = file.getPath();
            diskDir = diskDir.startsWith(".") ? diskDir.substring(1) : isAbsolutePath ? diskDir : File.separator + diskDir;
            ehcacheProperties.setDiskDir(isAbsolutePath ? diskDir : System.getProperty("user.dir") + diskDir);
            // 磁盘持久化目录（支持相对路径，若值为空，则不进行持久化）
            cacheManagerBuilder.with(CacheManagerBuilder.persistence(ehcacheProperties.getDiskDir()));
        }
        expiryPolicy = ehcacheProperties.getTtl() == 0L ? ExpiryPolicyBuilder.noExpiration() : ExpiryPolicyBuilder.timeToLiveExpiration(Duration.of(ehcacheProperties.getTtl(), ChronoUnit.SECONDS));
        LOGGER.trace(ehcacheProperties.toString());
    }

    private void initEhcacheProperties() {
        if (ehcacheProperties.getHeap() == null) {
            ehcacheProperties.setHeap(1000L);
        }
        if (ehcacheProperties.getOffHeap() == null) {
            ehcacheProperties.setOffHeap(100L);
        }
        if (StringUtils.isNotBlank(ehcacheProperties.getDiskDir()) && ehcacheProperties.getDisk() == null) {
            ehcacheProperties.setDisk(500L);
        }
        if (ehcacheProperties.getTtl() == null || ehcacheProperties.getTtl() < 0L) {
            ehcacheProperties.setTtl(7200L);
        }
    }

    @Override
    public Cache<String, String> getStringCache(String alias) {
        Cache<String, String> cache;
        try {
            cache = cacheManager.getCache(alias, String.class, String.class);
        } catch (Exception e) {
            LOGGER.warn("获取字符串缓存失败：alias = {}", alias);
            return null;
        }
        LOGGER.trace("获取字符串缓存成功：alias = {} and cache = {}", alias, cache);
        return cache;
    }

    @Override
    public String getStringCache(String alias, String key) {
        Cache<String, String> cache;
        try {
            cache = cacheManager.getCache(alias, String.class, String.class);
        } catch (Exception e) {
            LOGGER.warn("获取字符串缓存失败：alias = {} and key = {}", alias, key);
            return null;
        }
        if (cache == null) {
            LOGGER.error("获取字符串缓存失败：alias = {} and key = {}", alias, key);
            return null;
        }
        String value = cache.get(key);
        LOGGER.trace("获取字符串缓存成功：alias = {} and key = {} and value = {}", alias, key, value);
        return value;
    }

    @Override
    public Cache<String, String> setStringCache(String alias, String key, String value) {
        return setStringCache(alias, key, value, null, true);
    }

    @Override
    public Cache<String, String> setStringCache(String alias, String key, String value, Long ttl) {
        return setStringCache(alias, key, value, ttl, false);
    }

    private Cache<String, String> setStringCache(String alias, String key, String value, Long ttl, boolean isNull) {
        Cache<String, String> cache = null;
        boolean isNeedBuild;
        try {
            cache = cacheManager.getCache(alias, String.class, String.class);
            isNeedBuild = cache == null;
        } catch (Exception e) {
            isNeedBuild = true;
        }
        if (isNeedBuild) {
            CacheConfiguration<String, String> cacheConfiguration = CacheConfigurationBuilder
                    // 缓存数据 K 和 V 的数据类型，在 ehcache3.3 中必须指定缓存键值类型，如果使用中类型与配置的不同，会报类型转换异常
                    .newCacheConfigurationBuilder(String.class, String.class, resourcePoolsBuilder)
                    // ttl 过期时间（timeToLiveSeconds）
                    .withExpiry(ttl == null ? expiryPolicy : ttl == 0L ? ExpiryPolicyBuilder.noExpiration() : ExpiryPolicyBuilder.timeToLiveExpiration(Duration.of(ttl, ChronoUnit.SECONDS)))
                    .build();
            cacheManager = cacheManagerBuilder.withCache(alias, cacheConfiguration).build(true);
            cache = cacheManager.getCache(alias, String.class, String.class);
        }
        cache.put(key, value);
        LOGGER.trace("设置字符串缓存成功：alias = {} and key = {} and value = {} and ttl = {}", alias, key, value, ttl == null ? ehcacheProperties.getTtl() : ttl);
        return cache;
    }

    @Override
    public boolean deleteCache(String alias) {
        try {
            cacheManager.removeCache(alias);
            LOGGER.trace("删除 alias 缓存中的 key-value 成功：alias = {}", alias);
            return true;
        } catch (Exception e) {
            LOGGER.error("删除 alias 缓存中的 key-value 失败：alias = {}", alias);
            return false;
        }
    }
}
